package vn.edu.hut.soict.alignment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public abstract class BiDirectionalAlignment {
	public static final int NORMAL_MODE = 0;
	public static final int REVERSE_MODE = 1;

	protected List<String> source;
	protected List<String> destination;
	protected Map<Integer, List<Integer>> alignment;
	protected Map<Integer, List<Integer>> reversedAlignment;

	public List<String> getSource() {
		return source;
	}

	public List<String> getDestination() {
		return destination;
	}

	public Map<Integer, List<Integer>> getAlignment() {
		return alignment;
	}

	public Map<Integer, List<Integer>> getReversedAlignment() {
		return reversedAlignment;
	}

	protected void reverseAlignment() {
		reversedAlignment = new HashMap<Integer, List<Integer>>();
		for (Integer sourceIndex : alignment.keySet()) {
			List<Integer> destinationIndexList = alignment.get(sourceIndex);
			for (Integer destinationIndex : destinationIndexList) {
				addToAlignmentMap(reversedAlignment, destinationIndex, sourceIndex);
			}
		}
	}

	public boolean addToAlignment(Integer source, Integer destination) {
		return addToAlignmentMap(alignment, source, destination) && addToAlignmentMap(reversedAlignment, destination, source);
	}

	public boolean deleteFromAlignment(Integer source, Integer destination) {
		return deleteFromAlignmentMap(alignment, source, destination) && deleteFromAlignmentMap(reversedAlignment, destination, source);
	}

	public String alignmentToString(int mode) {
		Map<Integer, List<Integer>> map = null;
		List<String> sourceList = null;
		List<String> destinationList = null;

		if (mode == NORMAL_MODE) {
			map = alignment;
			sourceList = source;
			destinationList = destination;
		}
		else if (mode == REVERSE_MODE) {
			map = reversedAlignment;
			destinationList = source;
			sourceList = destination;
		}
		else {
			return null;
		}

		StringBuilder sb = new StringBuilder();
		for (Integer sourceIndex : map.keySet()) {
			if (sourceIndex != null) {
				List<Integer> destinationIndexList = map.get(sourceIndex);
				sb.append(sourceIndex + "#");

				for (Integer destinationIndex : destinationIndexList) {
					if (destinationIndex != null) {
						sb.append(destinationIndex + "|" + destinationList.get(destinationIndex) + "#");
					}
					else {
						sb.append(destinationList.size() + "|");
					}
				}
				sb.deleteCharAt(sb.length() - 1);
				sb.append(" ");
			}
		}

		// Splitting code of handling null key for right position in alignment
		// code
		int sourceIndex = sourceList.size();
		if (map.containsKey(null)) {
			List<Integer> destinationIndexList = map.get(null);
			sb.append(sourceIndex + "#");

			for (Integer destinationIndex : destinationIndexList) {
				if (destinationIndex != null) {
					sb.append(destinationIndex + "|" + destinationList.get(destinationIndex) + "#");
				}
				else {
					sb.append(destinationList.size() + "|");
				}
			}
			sb.deleteCharAt(sb.length() - 1);
			sb.append(" ");
		}

		return sb.toString();
	}

	protected boolean addToAlignmentMap(Map<Integer, List<Integer>> map, Integer key, Integer value) {
		List<Integer> alignList = null;
		if (key == null && value == null) {
			return false;
		}
		if (map.containsKey(key)) {
			if (value == null) {
				return false;
			}
			alignList = map.get(key);
			if (alignList.contains(value)) {
				return false;
			}
			if (alignList.contains(null)) {
				return false;
			}
		}
		else {
			alignList = new ArrayList<Integer>();
			map.put(key, alignList);
		}
		alignList.add(value);
		if (value != null) {
			Collections.sort(alignList);
		}
		return true;
	}

	protected boolean deleteFromAlignmentMap(Map<Integer, List<Integer>> map, Integer key, Integer value) {
		List<Integer> alignList = null;
		if (map.containsKey(key)) {
			alignList = map.get(key);
			if (alignList.contains(value)) {
				alignList.remove(value);
				if (alignList.size() == 0) {
					map.remove(key);
				}
				return true;
			}
		}
		return false;
	}

	public List<Integer> destinationIndexesAlignWith(Integer sourceIndex) {
		return alignment.get(sourceIndex);
	}

	public List<Integer> sourceIndexesAlignWith(Integer destinationIndex) {
		return reversedAlignment.get(destinationIndex);
	}

	public List<String> destinationAlignWith(int sourceIndex) {
		List<String> result = new ArrayList<String>();
		List<Integer> resultIndex = alignment.get(sourceIndex);
		for (Integer i : resultIndex) {
			if (i != null) {
				result.add(destination.get(i));
			}
		}
		return (result.size() > 0 ? result : null);
	}

	public List<String> sourceAlignWith(int destinationIndex) {
		List<String> result = new ArrayList<String>();
		List<Integer> resultIndex = reversedAlignment.get(destinationIndex);
		for (Integer i : resultIndex) {
			if (i != null) {
				result.add(source.get(i));
			}
		}
		return (result.size() > 0 ? result : null);
	}
}
